import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKind.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKind.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKind.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKind.OVERFLOW;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;


/**
 * This Class is responsible for listen to changes
 * in the Directory with files that are shared 
 * 
 * @author Vladimir Sarkisyan & Erez Levy
 *
 */
public class WatchDir {

    private final WatchService watcher;
    private final Map<WatchKey,Path> keys;
    private final boolean recursive;
    private boolean trace = false;
	BufferedReader in;
	PrintWriter out;
	 
	 
    @SuppressWarnings("unchecked")
    static <T> WatchEvent<T> cast(WatchEvent<?> event) {
        return (WatchEvent<T>)event;
    }
    
    /**
     * Register the given directory with the WatchService
     */
    private void register(Path dir) throws IOException {
        WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        if (trace) {
            Path prev = keys.get(key);
            if (prev == null) {
                System.out.format("register: %s\n", dir);
            } else {
                if (!dir.equals(prev)) {
                    System.out.format("update: %s -> %s\n", prev, dir);
                }
            }
        }
        keys.put(key, dir);
    }
    /**
     * Register the given directory, and all its sub-directories, with the
     * WatchService.
     */
    private void registerAll(final Path start) throws IOException {
        // register directory and sub-directories
        Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                throws IOException
            {
                register(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }
    
    
    /**
     * Creates a WatchService and registers the given directory
     */
    WatchDir(Path dir, boolean recursive, BufferedReader in, PrintWriter out) throws IOException {
        this.watcher = FileSystems.getDefault().newWatchService();
        this.keys = new HashMap<WatchKey,Path>();
        this.recursive = recursive;
        this.in=in;this.out=out;
        
        if (recursive) {
            System.out.format("Scanning %s ...\n", dir);
            registerAll(dir);
            System.out.println("Done.");
        } else {
            register(dir);
        }

        // enable trace after initial registration
        this.trace = true;
    }
    
    /**
     * Process all events for keys queued to the watcher
     * 
     * this  function is running in infinite loop that
     * that checking if there was changes in the Directory,
     * if was she get a even key from the windows
     */
    void processEvents() throws IOException {
    	System.out.println("in processEvents");
        for (;;) {
        	//to save the changes
        	ArrayList<String> changes = new ArrayList<String>();
        	//save the name/names of the file
        	ArrayList<Path> names = new ArrayList<Path>();
            // wait for key to be signalled
            WatchKey key;
            try {
                key = watcher.take();
            } catch (InterruptedException x) {
                return;
            }

            Path dir = keys.get(key);
            if (dir == null) {
                System.err.println("WatchKey not recognized!!");
                continue;
            }

            for (WatchEvent<?> event: key.pollEvents()) {
            	System.out.println("In the WatchEvent<?> event: key.pollEvents() FOR");
                WatchEvent.Kind kind = event.kind();
                
                
                
                // TBD - provide example of how OVERFLOW event is handled
                if (kind == OVERFLOW) {
                    continue;
                }

                // Context for directory entry event is the file name of entry
                WatchEvent<Path> ev = cast(event);
                Path name = ev.context();
                Path child = dir.resolve(name);

                // print out event
                //System.out.format("%s: %s\n", event.kind().name(), child);
                System.out.println(event.kind().name() + ": "+ child.getFileName());
                //save the changes in lists
                changes.add(event.kind().name());            
                names.add(child.getFileName());
                
                // if directory is created, and watching recursively, then
                // register it and its sub-directories
                if (recursive && (kind == ENTRY_CREATE)) {
                    if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
					    //registerAll(child);
					}
                }
            }
            System.out.println("after for");
            
            checkTheInputs(changes, names);

            // reset key and remove from set if directory no longer accessible
            boolean valid = key.reset();
            if (!valid) {
                keys.remove(key);

                // all directories are inaccessible
                if (keys.isEmpty()) {
                    break;
                }
            }
        }
    }
    //end of processEvents
    
    /**
     * The function check the events from the Directory
     * 
     * The function get the events and check them and after that write
     * to the PrintWriter out the proper message - "action fileName" 
     * that is send to the search program, that also connected to the DB 
     * of the user, and create a new FileClient that transfer also the new 
     * information or the new file to the server
     * 
     */
    public void checkTheInputs(ArrayList<String> changes, ArrayList<Path> names){
    	String request=null;
    	System.out.println("In checkTheInputs");
    	
    	//Executor executor;
    	
    	System.out.println("num changes: " + changes.size());
    	out.println("num changes: " + changes.size());
    	URI uri = names.get(0).toUri();
    	//System.out.println("uri: " + uri);
    	//System.out.println("uriPath: " +  uri.getPath());
    	File f = new File(uri);
    	System.out.println("file name: "+ f.getName());
    	
    	//case the file created
    	if(changes.size() == 2){
    		//CreateFileInDB createFile = (CreateFileInDB) ExecutorsHash.Instance.getExecutor("CreateFileInDB");
    		//set the name of the file
    		

    		//operation
    	    request="created ";
    	    //out.println(request);
    	    
    	    //file name send to the 
    	    request+=f.getName();
    	    out.println(request); 
    	    
    		//send to the server
    		new FileClient().sendFile(f.getName());
    		
    	}
    	//case that the file rename:
    	else if(changes.size() == 3){
    		
    		//RenameFileInDB renameFile = (RenameFileInDB) ExecutorsHash.Instance.getExecutor("RenameFileInDB");
    		//set the old file name
    		//for debug 
    		System.out.println("OldFileName - "+ f.getName());
    		//
    		//renameFile.setOldFileName(f.getName());
    		//for the new name
    		URI uri2 = names.get(1).toUri();
    		File f2 = new File(uri2);
    		//set the new name of the file
    		//for debug 
    		System.out.println("NewFileName - "+ f2.getName());
    		//
    		//renameFile.setNewFileName(f2.getName());
    		//renameFile.execute();
    		
    		//operation
    	    request="rename ";
    	    //out.println(request);
    	    
    	    //old file name
    	    request+=f.getName();
    	    request+=" ";
    	    //out.println(request); 
    	    
    	    //new file name
    	    request+=f2.getName();
    	    out.println(request); 
    	    
    	    //send to the server
    	    new FileClient().renameFile(f.getName(),f2.getName());
    	}
    	else{
    		//case the file deleted
    		if(changes.get(0).equals("ENTRY_DELETE")){
    			//DeleteFileInDB deleteFile = (DeleteFileInDB) ExecutorsHash.Instance.getExecutor("DeleteFileInDB");
        		//set the name of the file
        		//deleteFile.setName(f.getName());
        		//deleteFile.execute();
    			
        		//operation
        	    request="deleted ";
        	    //out.println(request);
        	    
        	    //file name
        	    request+=f.getName();
        	    out.println(request); 
        	    
        	  //send to the server
        	    new FileClient().deleteFile(f.getName());	    
    		}
    		
    		else if(changes.get(0).equals("ENTRY_MODIFY")){
    			
    			//ChangeFileValue changeFileValue = (ChangeFileValue) ExecutorsHash.Instance.getExecutor("ChangeFileValue");
    			//set the name of the file
    			//changeFileValue.setName(f.getName());
    			//changeFileValue.execute();
    			
           		//operation
        	    request="created ";
        	    //out.println(request);
        	    
        	    //file name
        	    request+=f.getName();
        	    out.println(request);
        	    
        		//send to the server
        		new FileClient().sendFile(f.getName());
    		}
    	}
    	
    }
}
